///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef __BUOLA_GEOMETRY_CRECT_H__
#define __BUOLA_GEOMETRY_CRECT_H__

#include <buola/utility/cpoint.h>
#include <buola/utility/csize.h>

namespace buola {

///\addtogroup geometry
///@{

////////////////////////////////////////////////////////////////////////////

/// 2D rectangle

/// Represents a rectangle in 2 dimensions
///
/// Be careful with this:
/// - (0,0,0,0) means empty rectangle
/// - for X, (0,0,0,0) means 1 pixel rectangle, so remember to do the conversion
///
////////////////////////////////////////////////////////////////////////////

DECLARE_NAME(Unbounded)

template<typename tType>
class CRect
{
    typedef std::numeric_limits<tType> TLimits;

public:
//! \name constructor and destructor
//@{
    //! default constructor (leaves uninitialized)
    CRect()=default;

    //! copy constructor
    CRect(const CRect&)=default;

    //! from int object
    template<typename tO,OEnableIf<detail::QAutomaticPointConversion<tO,tType>> = 0>
    CRect(const CRect<tO> &pO)
        :   l(pO.l)
        ,   t(pO.t)
        ,   r(undefined(pO.r)?undefined<tType>():(tType)pO.r)
        ,   b(undefined(pO.b)?undefined<tType>():(tType)pO.b)
    {}

    //! from int object
    template<typename tO,OEnableIfNot<detail::QAutomaticPointConversion<tO,tType>> = 0>
    explicit CRect(const CRect<tO> &pO)
        :   l(pO.l)
        ,   t(pO.t)
        ,   r(undefined(pO.r)?undefined<tType>():(tType)pO.r)
        ,   b(undefined(pO.b)?undefined<tType>():(tType)pO.b)
    {}

    //! from top-left and bottom-right coordinates
    CRect(tType pl,tType pt,tType pr,tType pb)
        :   l(pl)
        ,   t(pt)
        ,   r(pr)
        ,   b(pb)
    {}

    //! from a point (top-left corner) and a size
    CRect(const CPoint<tType> &pP,const CSize<tType> &pS)
        :   l(pP.x),
            t(pP.y),
            r(pP.x+pS.x),
            b(pP.y+pS.y)
    {}

    //! from two points

    //! The points can be specified in any order. It constructs the rectangle
    //! which has the two points as corners
    CRect(const CPoint<tType> &p1,const CPoint<tType> &p2)
        :   l(min(p1.x,p2.x))
        ,   t(min(p1.y,p2.y))
        ,   r(max(p1.x,p2.x))
        ,   b(max(p1.y,p2.y))
    {}

    //! undefined constructor
    CRect(NUndefined)
        :   l(0)
        ,   t(0)
        ,   r(undefined<tType>())
        ,   b(undefined<tType>())
        
    {}

    //! zero constructor
    CRect(NZero)
        :   l(0)
        ,   t(0)
        ,   r(0)
        ,   b(0)
    {}

    //! zero constructor
    CRect(NUnbounded)
        :   l(TLimits::min())
        ,   t(TLimits::min())
        ,   r(TLimits::max())
        ,   b(TLimits::max())
    {}
//@}

    //! \brief moves the rectangle (in-place version)
    //!
    //! Subtracts the point from each corner of the rectangle
    CRect &operator-=(const CPoint<tType> &pRH)
    {
        l-=pRH.x;
        t-=pRH.y;
        r-=pRH.x;
        b-=pRH.y;
        return *this;
    }

    //! \brief moves the rectangle (in-place version)
    //!
    //! Adds the point to each corner of the rectangle
    CRect &operator+=(const CPoint<tType> &pRH)
    {
        l+=pRH.x;
        t+=pRH.y;
        r+=pRH.x;
        b+=pRH.y;
        return *this;
    }

    //! \brief resizes the rectangle (in-place version)
    //!
    //! Subtracts the size from the top-left corner of the rectangle
    CRect &operator-=(const CSize<tType> &pRH)
    {
        l-=pRH.x;
        t-=pRH.y;
        return *this;
    }

    //! \brief resizes the rectangle (in-place version)
    //!
    //! Adds the size to the bottom-right corner of the rectangle
    CRect &operator+=(const CSize<tType> &pRH)
    {
        l+=pRH.x;
        t+=pRH.y;
        return *this;
    }

    //! comparison operator
    bool operator==(const CRect &pOther) const
    {
        return (l==pOther.l&&t==pOther.t&&r==pOther.r&&b==pOther.b);
    }

    //! inequality operator
    bool operator!=(const CRect &pOther) const
    {
        return (l!=pOther.l||t!=pOther.t||r!=pOther.r||b!=pOther.b);
    }

    //! returns true if the rectangle is empty (has zero area)
    bool empty()
    {
        return (l==r||t==b);
    }

    //! \brief returns true if the rectangle contains the point
    //!
    //! It also returns true if the point lies on the edge of the rectangle
    bool Contains(const CPoint<tType> &pOther) const
    {
        return (pOther.x>=l&&pOther.x<r&&pOther.y>=t&&pOther.y<b);
    }

    //! returns true if both rectangles intersect
    bool DoesIntersect(const CRect &pOther) const
    {
        return (l<pOther.r&&r>pOther.l&&t<pOther.b&&b>pOther.t);
    }

    //! returns true if the argument is totally container inside this rectangle
    bool Contains(const CRect &pOther) const
    {
        return (l<=pOther.l&&r>=pOther.r&&t<=pOther.t&&b>=pOther.b);
    }

    //! returns the center of the rectangle, as a CPoint<tType> object
    CPoint<tType> Center() const
    {
        return CPoint<tType>((l+r)/2,(t+b)/2);
    }

    //! makes sure that l<r and t<b
    void Normalize()
    {
        using std::swap;
        if(l>r)
        {
            swap(l,r);
        }

        if(t>b)
        {
            swap(t,b);
        }
    }

    //! returns the top-left corner
    CPoint<tType> TopLeft() const
    {
        return CPoint<tType>(l,t);
    }

    //! returns the top-right corner
    CPoint<tType> TopRight() const
    {
        return CPoint<tType>(r,t);
    }

    //! returns the bottom-right corner
    CPoint<tType> BottomRight() const
    {
        return CPoint<tType>(r,b);
    }

    //! returns the bottom-right corner
    CPoint<tType> BottomLeft() const
    {
        return CPoint<tType>(l,b);
    }

    //! \brief returns the inscribed rectangle at an offset \p pA
    //!
    //! the distance between each side of this rectangle and the correspondent
    //! side of the returned rectangle will be \p pA
    CRect Offset(tType pA) const
    {
        return CRect(l+pA,t+pA,r-pA,b-pA);
    }

    ///modifies the rectangle so that it is the smallest rectangle that contains the current rectangle and pP
    void AddPoint(const CPoint<tType> &pP)
    {
        if(Undefined())
        {
            l=pP.x;
            t=pP.y;
            r=pP.x;
            b=pP.y;
        }
        else
        {
            if(pP.x<l)
                l=pP.x;
            else if(pP.x>r)
                r=pP.x;
            if(pP.y<t)
                t=pP.y;
            else if(pP.y>b)
                b=pP.y;
        }
    }
    
    //! returns the size of the rectangle
    CSize<tType> Size() const
    {
        return {r-l,b-t};
    }

    //! returns the width of the rectangle
    tType w() const
    {
        return r-l;
    }

    //! returns the height of the rectangle
    tType h() const
    {
        return b-t;
    }

    //! returns the area of the rectangle
    tType Area() const
    {
        return ((r-l)*(b-t));
    }

    //! returns true if the rectangle is undefined
    bool Undefined() const
    {
        return (undefined(r)||undefined(b));
    }

    bool Unbounded() const
    {
        return (l==TLimits::min()&&t==TLimits::min()&&r==TLimits::max()&&b==TLimits::max());
    }

    tType l;   //!< left coordinate
    tType t;   //!< top coordinate
    tType r;   //!< right coordinate
    tType b;   //!< bottom coordinate
};

//! \relates CRect
///returns the union between both rectangles
template<typename tType>
inline CRect<tType> bounding_box(const CRect<tType> &pLH,const CRect<tType> &pRH)
{
    tType ll=min(pLH.l,pRH.l);
    tType lt=min(pLH.t,pRH.t);
    tType lr=max(pLH.r,pRH.r);
    tType lb=max(pLH.b,pRH.b);

    return(CRect<tType>(ll,lt,lr,lb));
}

//! \relates CRect
///returns the intersection between both rectangles
template<typename tType>
inline CRect<tType> intersection(const CRect<tType> &pLH,const CRect<tType> &pRH)
{
    tType ll=max(pLH.l,pRH.l);
    tType lt=max(pLH.t,pRH.t);
    tType lr=max(min(pLH.r,pRH.r),ll);
    tType lb=max(min(pLH.b,pRH.b),lt);

    return {ll,lt,lr,lb};
}

//! \relates CRect
///returns the largest rectangle with integer coordinates that is contained by the given rectangle
template<typename tType>
inline CRect<tType> floor(const CRect<tType> &pR)
{
    return {ceil(pR.l),ceil(pR.t),floor(pR.r),floor(pR.b)};
}

//! \relates CRect
///returns the smallest rectangle with integer coordinates that contains the given rectangle
template<typename tType>
inline CRect<tType> ceil(const CRect<tType> &pR)
{
    return {floor(pR.l),floor(pR.t),ceil(pR.r),ceil(pR.b)};
}

//! \relates CRect
//! \relates CPoint
//! \brief moves the rectangle
//!
//! Subtracts the point from each corner of the rectangle
template<typename tType>
inline CRect<tType> operator-(const CRect<tType> &pLH,const CPoint<tType> &pRH)
{
    return CRect<tType>(pLH.l-pRH.x,pLH.t-pRH.y,pLH.r-pRH.x,pLH.b-pRH.y);
}

//! \relates CRect
//! \relates CPoint
//! \brief moves the rectangle
//!
//! Adds the point to each corner of the rectangle
template<typename tType>
inline CRect<tType> operator+(const CRect<tType> &pLH,const CPoint<tType> &pRH)
{
    return CRect<tType>(pLH.l+pRH.x,pLH.t+pRH.y,pLH.r+pRH.x,pLH.b+pRH.y);
}

//! \relates CSize
//! \relates CRect
//! \brief resizes the rectangle
//!
//! Subtracts the size from the bottom-right corner of the rectangle
template<typename tType>
inline CRect<tType> operator-(const CRect<tType> &pLH,const CSize<tType> &pRH)
{
    return CRect<tType>(pLH.l,pLH.t,pLH.r-pRH.x,pLH.b-pRH.y);
}

//! \relates CSize
//! \relates CRect
//! \brief resizes the rectangle
//!
//! Adds the size to the bottom-right corner of the rectangle
template<typename tType>
inline CRect<tType> operator+(const CRect<tType> &pLH,const CSize<tType> &pRH)
{
    return CRect<tType>(pLH.l,pLH.t,pLH.r+pRH.x,pLH.b+pRH.y);
}

//! \relates CRect
//! \brief subtracts each coordinate of a rectangle from the other
template<typename tType>
inline CRect<tType> operator-(const CRect<tType> &pLH,const CRect<tType> &pRH)
{
    return CRect<tType>(pLH.l-pRH.l,pLH.t-pRH.t,pLH.r-pRH.r,pLH.b-pRH.b);
}

//! \relates CRect
//! \brief adds each coordinate of a rectangle to the other
template<typename tType>
inline CRect<tType> operator+(const CRect<tType> &pLH,const CRect<tType> &pRH)
{
    return CRect<tType>(pLH.l+pRH.l,pLH.t+pRH.t,pLH.r+pRH.r,pLH.b+pRH.b);
}

//! \relates CRect
//! \brief multiplies each coordinate of a rectangle by a scalar
template<typename tType>
inline CRect<tType> operator*(const CRect<tType> &pLH,tType pRH)
{
    return CRect<tType>(pLH.l*pRH,pLH.t*pRH,pLH.r*pRH,pLH.b*pRH);
}

//! \relates CRect
//! \brief divides a rectangle by a scalar
template<typename tType>
inline CRect<tType> operator/(const CRect<tType> &pLH,tType pRH)
{
    return CRect<tType>(pLH.l/pRH,pLH.t/pRH,pLH.r/pRH,pLH.b/pRH);
}

//! \relates CPoint
//! \relates CSize
//! \relates CRect
//! \brief returns the rectangle which has the top-left coordinate at the
//! point given by the left-hand operand and the size given by the
//! right-hand operand
template<typename tType>
inline CRect<tType> operator*(const CPoint<tType> &pP,const CSize<tType>& pS)
{
    return CRect<tType>(pP.x,pP.y,pP.x+pS.x,pP.y+pS.y);
}

template<typename tType>
inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const CRect<tType> &pRect)
{
    pWriter << CPoint<tType>(pRect.l,pRect.t) << '-' << CPoint<tType>(pRect.r,pRect.b);
    return pWriter;
}

typedef CRect<double> CRect_d;
typedef CRect<float> CRect_f;
typedef CRect<int> CRect_i;

///@}

/*namespace buola*/ }

#endif
